package com.mysteel.micro.watermark.image;

import org.apache.commons.lang.StringUtils;
import org.bytedeco.javacv.Java2DFrameUtils;
import org.bytedeco.opencv.opencv_core.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Base64;

import static org.bytedeco.opencv.global.opencv_core.*;
import static org.bytedeco.opencv.global.opencv_imgcodecs.imread;
import static org.bytedeco.opencv.global.opencv_imgcodecs.imwrite;
import static org.bytedeco.opencv.global.opencv_imgproc.FONT_HERSHEY_TRIPLEX;
import static org.bytedeco.opencv.global.opencv_imgproc.putText;

@Component
public class BindWatermarkUtil {
    private static Logger logger = LoggerFactory.getLogger(BindWatermarkUtil.class);
    @Autowired
    private Environment environment;

    @PostConstruct
    public void loadSource(){
        // linux环境需要加载包 javacv
        String profiles = environment.getProperty("env");

        logger.info("当前环境：{}", profiles);
        if (StringUtils.isNotBlank(profiles) && !StringUtils.equals(profiles, "dev")) {
            logger.info("加载环境C环境包：{}", profiles);
            System.load("/usr/lib/libstdc++.so.6");
            System.load("/usr/lib/libgcc_s.so.1");
        }
        Mat matDemo = new Mat();
    }


    public static void addImageWatermarkWithText(String sourcePath, String watermark, String targetPath) throws IOException {
        long timeStart = System.currentTimeMillis();
        Path inputPath = Paths.get(sourcePath);
        Path outPath = Paths.get(targetPath);

        if (!Files.exists(inputPath)) {
            throw new IOException("File not exist");
        }
        Mat imgMat = null;
        Mat watermarkedMat = null;
        Mat cutMat = null;
        Rect rect = null;
        try {
            imgMat = imread(inputPath.toAbsolutePath().toString());
            watermarkedMat = addImageWatermarkWithText(imgMat, watermark);
            // 裁剪黑边
            rect = new Rect(0, 0, imgMat.cols(), imgMat.rows());
            cutMat = new Mat(watermarkedMat, rect);
            imwrite(outPath.toAbsolutePath().toString(), cutMat);
            logger.info("图片盲水印添加成功,内存已释放，path={}，耗时={}ms",targetPath,System.currentTimeMillis() - timeStart);
        } catch (Exception e) {
            throw new IOException("Watermarking Process Error", e);
        }finally {
            // 释放内存，避免老年代积压过多
            if (imgMat != null) {
                imgMat.deallocate();
            }
            if (watermarkedMat != null) {
                watermarkedMat.deallocate();
            }
            if (cutMat != null) {
                cutMat.deallocate();
            }
            if (rect != null) {
                rect.deallocate();
            }
        }
    }
    public static void saveMatToDisk(Mat image,String savePath) throws IOException {
        Path outPath = Paths.get(savePath);

        try {
            imwrite(outPath.toAbsolutePath().toString(), image);
        }catch (Exception e){
            logger.error("保存Mat对象失败{}",savePath,e);
        }finally {
            if(image != null){
                image.deallocate();
            }
        }
    }

    public static String getBase64(Mat image) throws IOException {
        return getBase64(mat2bytes(image));
    }

    public static String getBase64(byte[] bytes) {
        return "data:image/png;base64," + Base64.getEncoder().encodeToString(bytes);
    }

    public static byte[] mat2bytes(Mat image) throws IOException {
        try(ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
            BufferedImage bufferedImage = Java2DFrameUtils.toBufferedImage(image);
            ImageIO.write(bufferedImage, "png", baos);
            // 释放内存，避免老年代积压过多
            image.deallocate();
            return baos.toByteArray();
        }catch (Exception ignored){

        }
        return null;
    }




    public static Mat getImageWatermarkWithText(Mat image) {
        MatVector allPlanes = new MatVector();
        Mat complexImage = getDFT(image, allPlanes);
        return createOptimizedMagnitude(complexImage);
    }

    public static Mat getDFT(Mat image, MatVector allPlanes) {
        MatVector planes = new MatVector();
        Mat complexImage = new Mat();
        Mat padded = splitSrc(image, allPlanes);
        padded.convertTo(padded, CV_32F);
        planes.push_back(padded);
        planes.push_back(Mat.zeros(padded.size(), CV_32F).asMat());
        merge(planes, complexImage);
        // dft
        dft(complexImage, complexImage);
        // 释放内存，避免老年代积压过多
        padded.deallocate();
        planes.deallocate();
        return complexImage;
    }

    private static Mat splitSrc(Mat mat, MatVector allPlanes) {
        mat = optimizeImageDim(mat);
        split(mat, allPlanes);
        Mat padded = new Mat();
        if (allPlanes.size() > 1) {
            for (int i = 0; i < allPlanes.size(); i++) {
                if (i == 0) {
                    padded = allPlanes.get(i);
                    break;
                }
            }
        } else {
            padded = mat;
        }
        return padded;
    }
    private static Mat optimizeImageDim(Mat image) {
        //优化尺寸会添加黑边
        Mat padded = new Mat();
        int addPixelRows = getOptimalDFTSize(image.rows());
        int addPixelCols = getOptimalDFTSize(image.cols());
        copyMakeBorder(image, padded, 0, addPixelRows - image.rows(), 0, addPixelCols - image.cols(),
                BORDER_CONSTANT, Scalar.all(0));
        return padded;
    }
    private static Mat antitransformImage(Mat complexImage, MatVector allPlanes) {
        Mat invDFT = new Mat();
        idft(complexImage, invDFT, DFT_SCALE | DFT_REAL_OUTPUT, 0);
        Mat restoredImage = new Mat();
        invDFT.convertTo(restoredImage, CV_8U);
        if (allPlanes.size() == 0) {
            allPlanes.push_back(restoredImage);
        } else {
            allPlanes.put(0, restoredImage);
        }
        Mat lastImage = new Mat();
        merge(allPlanes, lastImage);
        // 释放内存，避免老年代积压过多
        complexImage.deallocate();
        restoredImage.deallocate();
        allPlanes.deallocate();
        return lastImage;
    }

    private static Mat createOptimizedMagnitude(Mat complexImage) {
        MatVector newPlanes = new MatVector();
        Mat mag = new Mat();
        split(complexImage, newPlanes);
        magnitude(newPlanes.get(0), newPlanes.get(1), mag);
        add(Mat.ones(mag.size(), CV_32F).asMat(), mag, mag);
        log(mag, mag);
        shiftDFT(mag);
        mag.convertTo(mag, CV_8UC1);
        normalize(mag, mag, 0, 255, NORM_MINMAX, CV_8UC1, null);
        // 释放内存，避免老年代积压过多
        newPlanes.deallocate();
        complexImage.deallocate();
        return mag;
    }

    private static void shiftDFT(Mat image) {
        image = image.apply(new Rect(0, 0, image.cols() & -2, image.rows() & -2));
        int cx = image.cols() / 2;
        int cy = image.rows() / 2;

        Mat q0 = new Mat(image, new Rect(0, 0, cx, cy));
        Mat q1 = new Mat(image, new Rect(cx, 0, cx, cy));
        Mat q2 = new Mat(image, new Rect(0, cy, cx, cy));
        Mat q3 = new Mat(image, new Rect(cx, cy, cx, cy));
        Mat tmp = new Mat();
        q0.copyTo(tmp);
        q3.copyTo(q0);
        tmp.copyTo(q3);
        q1.copyTo(tmp);
        q2.copyTo(q1);
        tmp.copyTo(q2);
        // 释放内存，避免老年代积压过多
        q0.deallocate();
        q1.deallocate();
        q2.deallocate();
        q3.deallocate();
        tmp.deallocate();
    }

    public static String getImageWatermarkWithTextOfBase64(String srcPath) throws IOException {
        return getBase64(getImageWatermarkWithText(srcPath));
    }
    public static void getImageWatermarkWithText(String srcPath,String savePath) throws IOException {
         saveMatToDisk(getImageWatermarkWithText(srcPath),savePath);
    }

    public static Mat getImageWatermarkWithText(String srcPath) throws IOException {
        long timeStart = System.currentTimeMillis();
        Path path = Paths.get(srcPath);
        if (!Files.exists(path)) {
            throw new IOException("File not exist");
        }
        Mat mat = imread(path.toAbsolutePath().toString());
        Mat imageWatermarkWithText = getImageWatermarkWithText(mat);
        // 释放内存，避免老年代积压过多
        mat.deallocate();
        logger.info("图片盲水印解析成功,内存已释放，path={}，耗时={}ms",srcPath,System.currentTimeMillis() - timeStart);
        return imageWatermarkWithText;
    }

    public static Mat addImageWatermarkWithText(Mat image, String watermarkText) {
        MatVector allPlanes = new MatVector();
        Mat complexImage = getDFT(image, allPlanes);
        // 添加文本水印
        int rows = image.rows()/2;
        int cols = image.cols()/2;

        int baseX = 200;
        int baseY = 250;
        // 按倍数增加间距，并不超过最大间距
        if(cols > 500){
            baseX = baseX * (cols/500);
            baseX = Math.min(baseX, 1000);
        }
        if(rows > 500){
            baseY = baseY * (rows/500);
            baseY = Math.min(baseY, 500);
        }
        int xLo = 1;
        for(int x = 40; x < cols;x += baseX){
            int yLo = 1;
            for(int y = 150; y < rows; y += baseY) {
                if(needBind(xLo,yLo)) {
                    addWaterMarkRound(complexImage, watermarkText, x, y);
                }
                yLo++;
            }
            xLo++;
        }

        return antitransformImage(complexImage, allPlanes);
    }

    /**
     * 进行坐标判断，跳跃打印水印
     * @param x
     * @param y
     * @return
     */
    public static boolean needBind(int x,int y){
        boolean xBool = x % 2 != 0;
        boolean yBool = y % 2 != 0;
        if((xBool && yBool) || (!xBool && !yBool )){
            return true;
        }
        return false;
    }

    public static void addWaterMarkRound(Mat complexImage, String watermarkText,int x,int y){
        Scalar scalar = new Scalar(0x00, 0);
        Point point = new Point(x, y);
        putText(complexImage, watermarkText, point,FONT_HERSHEY_TRIPLEX, 1.5, scalar, 3, 5, false);
        flip(complexImage, complexImage, -1);
        putText(complexImage, watermarkText, point,FONT_HERSHEY_TRIPLEX, 1.5, scalar, 3, 5, true);
        flip(complexImage, complexImage, -1);
        putText(complexImage, watermarkText, point,FONT_HERSHEY_TRIPLEX, 1.5, scalar, 3, 5, false);
        flip(complexImage, complexImage, 0);
        putText(complexImage, watermarkText, point,FONT_HERSHEY_TRIPLEX, 1.5, scalar, 3, 5, true);
        flip(complexImage, complexImage, 0);
    }

    public static void main(String[] args) throws IOException {

     //   addImageWatermarkWithText("D:\\Users\\Desktop\\111\\333.png","Mysteel",
              //  "D:\\Users\\Desktop\\111\\333_out.png");
       // addImageWatermarkWithText("D:\\Users\\Desktop\\74.png","0983","D:\\Users\\Desktop\\74_out.png");
        Mat imageWatermarkWithText = getImageWatermarkWithText("D:\\Users\\Desktop\\111\\113423.jpg");
        saveMatToDisk(imageWatermarkWithText,"D:\\Users\\Desktop\\111\\113423_de.png");
        System.out.println(getBase64(imageWatermarkWithText));
    }
}